/*
 *    GeoTools - The Open Source Java GIS Toolkit
 *    http://geotools.org
 *
 *    (C) 2004-2008, Open Source Geospatial Foundation (OSGeo)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */
package org.geotools.referencing;

import java.io.IOException;
import java.io.Writer;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
import java.util.function.Predicate;
import org.geotools.api.metadata.Identifier;
import org.geotools.api.metadata.citation.Citation;
import org.geotools.api.referencing.AuthorityFactory;
import org.geotools.api.referencing.Factory;
import org.geotools.api.referencing.crs.CRSAuthorityFactory;
import org.geotools.api.referencing.crs.CRSFactory;
import org.geotools.api.referencing.cs.CSAuthorityFactory;
import org.geotools.api.referencing.cs.CSFactory;
import org.geotools.api.referencing.datum.DatumAuthorityFactory;
import org.geotools.api.referencing.datum.DatumFactory;
import org.geotools.api.referencing.operation.CoordinateOperationAuthorityFactory;
import org.geotools.api.referencing.operation.CoordinateOperationFactory;
import org.geotools.api.referencing.operation.MathTransformFactory;
import org.geotools.metadata.iso.citation.Citations;
import org.geotools.referencing.factory.gridshift.GridShiftLocator;
import org.geotools.util.Arguments;
import org.geotools.util.LazySet;
import org.geotools.util.factory.FactoryCreator;
import org.geotools.util.factory.FactoryFinder;
import org.geotools.util.factory.FactoryRegistry;
import org.geotools.util.factory.FactoryRegistryException;
import org.geotools.util.factory.GeoTools;
import org.geotools.util.factory.Hints;

/**
 * Defines static methods used to access the application's default {@linkplain Factory factory}
 * implementation.
 *
 * <p>To declare a factory implementation, a services subdirectory is placed within the {@code
 * META-INF} directory that is present in every JAR file. This directory contains a file for each
 * factory interface that has one or more implementation classes present in the JAR file. For
 * example, if the JAR file contained a class named {@code com.mycompany.DatumFactoryImpl} which
 * implements the {@link DatumFactory} interface, the JAR file would contain a file named:
 *
 * <blockquote>
 *
 * <pre>META-INF/services/org.geotools.api.referencing.datum.DatumFactory</pre>
 *
 * </blockquote>
 *
 * <p>containing the line:
 *
 * <blockquote>
 *
 * <pre>com.mycompany.DatumFactoryImpl</pre>
 *
 * </blockquote>
 *
 * <p>If the factory classes implements {@link javax.imageio.spi.RegisterableService}, it will be
 * notified upon registration and deregistration. Note that the factory classes should be
 * lightweight and quick to load. Implementations of these interfaces should avoid complex
 * dependencies on other classes and on native code. The usual pattern for more complex services is
 * to register a lightweight proxy for the heavyweight service.
 *
 * <H2>Note on factory ordering</H2>
 *
 * <p>This class is thread-safe. However, calls to any {@link #setAuthorityOrdering} or {@link
 * #setVendorOrdering} methods have a system-wide effect. If two threads or two applications need a
 * different ordering, they shall manage their own instance of {@link FactoryRegistry}. This {@code
 * FactoryFinder} class is simply a convenience wrapper around a {@code FactoryRegistry} instance.
 *
 * @since 2.4
 * @version $Id$
 * @author Martin Desruisseaux (IRD)
 */
public final class ReferencingFactoryFinder extends FactoryFinder {
    /** The service registry for this manager. Will be initialized only when first needed. */
    private static volatile FactoryRegistry registry;

    /** The authority names. Will be created only when first needed. */
    private static Set<String> authorityNames;

    /** Do not allows any instantiation of this class. */
    private ReferencingFactoryFinder() {}

    /**
     * Returns the service registry. The registry will be created the first time this method is
     * invoked.
     */
    private static FactoryRegistry getServiceRegistry() {
        assert Thread.holdsLock(ReferencingFactoryFinder.class);
        if (registry == null) {
            registry =
                    new FactoryCreator(
                            new Class<?>[] {
                                DatumFactory.class,
                                CSFactory.class,
                                CRSFactory.class,
                                DatumAuthorityFactory.class,
                                CSAuthorityFactory.class,
                                CRSAuthorityFactory.class,
                                MathTransformFactory.class,
                                CoordinateOperationFactory.class,
                                CoordinateOperationAuthorityFactory.class,
                                GridShiftLocator.class
                            });
        }
        return registry;
    }

    /** Returns the names of all currently registered authorities. */
    public static synchronized Set<String> getAuthorityNames() {
        /*
         * IMPORTANT: Return the same Set instance (unmodifiable) as long as there is no change
         * in the list of registered factories, and create a new instance in case of changes.
         * 'add/removeAuthorityFactory(...)' and 'scanForPlugins()' methods reset 'authorityNames'
         * to null, which will cause the creation of a new Set instance. Some implementations like
         * AllAuthoritiesFactory rely on this behavior as a way to be notified of registration
         * changes for clearing their cache.
         */
        if (authorityNames == null) {
            authorityNames = new LinkedHashSet<>();
            final Hints hints = EMPTY_HINTS;
            loop:
            for (int i = 0; ; i++) {
                final Set<? extends AuthorityFactory> factories;
                switch (i) {
                    case 0:
                        factories = getCRSAuthorityFactories(hints);
                        break;
                    case 1:
                        factories = getCSAuthorityFactories(hints);
                        break;
                    case 2:
                        factories = getDatumAuthorityFactories(hints);
                        break;
                    case 3:
                        factories = getCoordinateOperationAuthorityFactories(hints);
                        break;
                    default:
                        break loop;
                }
                for (final AuthorityFactory factory : factories) {
                    final Citation authority = factory.getAuthority();
                    if (authority != null) {
                        authorityNames.add(Citations.getIdentifier(authority));
                        for (final Identifier id : authority.getIdentifiers()) {
                            authorityNames.add(id.getCode());
                        }
                    }
                }
            }
            authorityNames = Collections.unmodifiableSet(authorityNames);
        }
        return authorityNames;
    }

    /**
     * Returns all providers of the specified category.
     *
     * @param category The factory category.
     * @param hints An optional map of hints, or {@code null} if none.
     * @return Set of available factory implementations.
     */
    private static synchronized <T extends Factory> Set<T> getFactories(
            final Class<T> category, Hints hints) {
        hints = mergeSystemHints(hints);
        return new LazySet<>(getServiceRegistry().getFactories(category, null, hints));
    }

    /**
     * Returns a provider of the specified category.
     *
     * @param category The factory category.
     * @param hints An optional map of hints, or {@code null} if none.
     * @param key The hint key to use for searching an implementation.
     * @return The first factory that matches the supplied hints.
     * @throws FactoryRegistryException if no implementation was found or can be created for the
     *     specified interface.
     */
    private static synchronized <T extends Factory> T getFactory(
            final Class<T> category, Hints hints, final Hints.Key key)
            throws FactoryRegistryException {
        hints = mergeSystemHints(hints);
        return getServiceRegistry().getFactory(category, null, hints, key);
    }

    /**
     * Returns the first implementation of a factory matching the specified hints. If no
     * implementation matches, a new one is created if possible or an exception is thrown otherwise.
     * If more than one implementation is registered and an {@linkplain #setVendorOrdering ordering
     * is set}, then the preferred implementation is returned. Otherwise an arbitrary one is
     * selected.
     *
     * @param category The authority factory type.
     * @param authority The desired authority (e.g. "EPSG").
     * @param hints An optional map of hints, or {@code null} if none.
     * @param key The hint key to use for searching an implementation.
     * @return The first authority factory that matches the supplied hints.
     * @throws FactoryRegistryException if no implementation was found or can be created for the
     *     specfied interface.
     */
    private static synchronized <T extends AuthorityFactory> T getAuthorityFactory(
            final Class<T> category, final String authority, Hints hints, final Hints.Key key)
            throws FactoryRegistryException {
        hints = mergeSystemHints(hints);
        return getServiceRegistry()
                .getFactory(category, new AuthorityFilter(authority), hints, key);
    }

    /**
     * Returns the first implementation of {@link DatumFactory} matching the specified hints. If no
     * implementation matches, a new one is created if possible or an exception is thrown otherwise.
     * If more than one implementation is registered and an {@linkplain #setVendorOrdering ordering
     * is set}, then the preferred implementation is returned. Otherwise an arbitrary one is
     * selected.
     *
     * @param hints An optional map of hints, or {@code null} if none.
     * @return The first datum factory that matches the supplied hints.
     * @throws FactoryRegistryException if no implementation was found or can be created for the
     *     {@link DatumFactory} interface.
     */
    public static DatumFactory getDatumFactory(final Hints hints) throws FactoryRegistryException {
        return getFactory(DatumFactory.class, hints, Hints.DATUM_FACTORY);
    }

    /**
     * Returns a set of all available implementations for the {@link DatumFactory} interface.
     *
     * @param hints An optional map of hints, or {@code null} if none.
     * @return Set of available datum factory implementations.
     */
    public static Set<DatumFactory> getDatumFactories(final Hints hints) {
        return getFactories(DatumFactory.class, hints);
    }

    /**
     * Returns the first implementation of {@link CSFactory} matching the specified hints. If no
     * implementation matches, a new one is created if possible or an exception is thrown otherwise.
     * If more than one implementation is registered and an {@linkplain #setVendorOrdering ordering
     * is set}, then the preferred implementation is returned. Otherwise an arbitrary one is
     * selected.
     *
     * @param hints An optional map of hints, or {@code null} if none.
     * @return The first coordinate system factory that matches the supplied hints.
     * @throws FactoryRegistryException if no implementation was found or can be created for the
     *     {@link CSFactory} interface.
     */
    public static CSFactory getCSFactory(final Hints hints) throws FactoryRegistryException {
        return getFactory(CSFactory.class, hints, Hints.CS_FACTORY);
    }

    /**
     * Returns a set of all available implementations for the {@link CSFactory} interface.
     *
     * @param hints An optional map of hints, or {@code null} if none.
     * @return Set of available coordinate system factory implementations.
     */
    public static Set<CSFactory> getCSFactories(final Hints hints) {
        return getFactories(CSFactory.class, hints);
    }

    /**
     * Returns the first implementation of {@link CRSFactory} matching the specified hints. If no
     * implementation matches, a new one is created if possible or an exception is thrown otherwise.
     * If more than one implementation is registered and an {@linkplain #setVendorOrdering ordering
     * is set}, then the preferred implementation is returned. Otherwise an arbitrary one is
     * selected.
     *
     * @param hints An optional map of hints, or {@code null} if none.
     * @return The first coordinate reference system factory that matches the supplied hints.
     * @throws FactoryRegistryException if no implementation was found or can be created for the
     *     {@link CRSFactory} interface.
     */
    public static CRSFactory getCRSFactory(final Hints hints) throws FactoryRegistryException {
        return getFactory(CRSFactory.class, hints, Hints.CRS_FACTORY);
    }

    /**
     * Returns a set of all available implementations for the {@link CRSFactory} interface.
     *
     * @param hints An optional map of hints, or {@code null} if none.
     * @return Set of available coordinate reference system factory implementations.
     */
    public static Set<CRSFactory> getCRSFactories(final Hints hints) {
        return getFactories(CRSFactory.class, hints);
    }

    /**
     * Returns the first implementation of {@link CoordinateOperationFactory} matching the specified
     * hints. If no implementation matches, a new one is created if possible or an exception is
     * thrown otherwise. If more than one implementation is registered and an {@linkplain
     * #setVendorOrdering ordering is set}, then the preferred implementation is returned. Otherwise
     * an arbitrary one is selected.
     *
     * <p>Hints that may be understood includes {@link Hints#MATH_TRANSFORM_FACTORY
     * MATH_TRANSFORM_FACTORY}, {@link Hints#DATUM_SHIFT_METHOD DATUM_SHIFT_METHOD}, {@link
     * Hints#LENIENT_DATUM_SHIFT LENIENT_DATUM_SHIFT} and {@link Hints#VERSION VERSION}.
     *
     * @param hints An optional map of hints, or {@code null} if none.
     * @return The first coordinate operation factory that matches the supplied hints.
     * @throws FactoryRegistryException if no implementation was found or can be created for the
     *     {@link CoordinateOperationFactory} interface.
     */
    public static CoordinateOperationFactory getCoordinateOperationFactory(final Hints hints)
            throws FactoryRegistryException {
        return getFactory(
                CoordinateOperationFactory.class, hints, Hints.COORDINATE_OPERATION_FACTORY);
    }

    /**
     * Returns a set of all available implementations for the {@link CoordinateOperationFactory}
     * interface.
     *
     * @param hints An optional map of hints, or {@code null} if none.
     * @return Set of available coordinate operation factory implementations.
     */
    public static Set<CoordinateOperationFactory> getCoordinateOperationFactories(
            final Hints hints) {
        return getFactories(CoordinateOperationFactory.class, hints);
    }

    /**
     * Returns the first implementation of {@link DatumAuthorityFactory} matching the specified
     * hints. If no implementation matches, a new one is created if possible or an exception is
     * thrown otherwise. If more than one implementation is registered and an {@linkplain
     * #setVendorOrdering ordering is set}, then the preferred implementation is returned. Otherwise
     * an arbitrary one is selected.
     *
     * @param authority The desired authority (e.g. "EPSG").
     * @param hints An optional map of hints, or {@code null} if none.
     * @return The first datum authority factory that matches the supplied hints.
     * @throws FactoryRegistryException if no implementation was found or can be created for the
     *     {@link DatumAuthorityFactory} interface.
     */
    public static DatumAuthorityFactory getDatumAuthorityFactory(
            final String authority, final Hints hints) throws FactoryRegistryException {
        return getAuthorityFactory(
                DatumAuthorityFactory.class, authority, hints, Hints.DATUM_AUTHORITY_FACTORY);
    }

    /**
     * Returns a set of all available implementations for the {@link DatumAuthorityFactory}
     * interface.
     *
     * @param hints An optional map of hints, or {@code null} if none.
     * @return Set of available datum authority factory implementations.
     */
    public static Set<DatumAuthorityFactory> getDatumAuthorityFactories(final Hints hints) {
        return getFactories(DatumAuthorityFactory.class, hints);
    }

    /**
     * Returns the first implementation of {@link CSAuthorityFactory} matching the specified hints.
     * If no implementation matches, a new one is created if possible or an exception is thrown
     * otherwise. If more than one implementation is registered and an {@linkplain
     * #setVendorOrdering ordering is set}, then the preferred implementation is returned. Otherwise
     * an arbitrary one is selected.
     *
     * <p>Hints that may be understood includes {@link Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER
     * FORCE_LONGITUDE_FIRST_AXIS_ORDER}, {@link Hints#FORCE_STANDARD_AXIS_UNITS
     * FORCE_STANDARD_AXIS_UNITS} and {@link Hints#FORCE_STANDARD_AXIS_DIRECTIONS
     * FORCE_STANDARD_AXIS_DIRECTIONS} and {@link Hints#VERSION VERSION}.
     *
     * @param authority The desired authority (e.g. "EPSG").
     * @param hints An optional map of hints, or {@code null} if none.
     * @return The first coordinate system authority factory that matches the supplied hints.
     * @throws FactoryRegistryException if no implementation was found or can be created for the
     *     {@link CSAuthorityFactory} interface.
     */
    public static CSAuthorityFactory getCSAuthorityFactory(
            final String authority, final Hints hints) throws FactoryRegistryException {
        return getAuthorityFactory(
                CSAuthorityFactory.class, authority, hints, Hints.CS_AUTHORITY_FACTORY);
    }

    /**
     * Returns a set of all available implementations for the {@link CSAuthorityFactory} interface.
     *
     * @param hints An optional map of hints, or {@code null} if none.
     * @return Set of available coordinate system authority factory implementations.
     */
    public static Set<CSAuthorityFactory> getCSAuthorityFactories(final Hints hints) {
        return getFactories(CSAuthorityFactory.class, hints);
    }

    /**
     * Returns the first implementation of {@link CRSAuthorityFactory} matching the specified hints.
     * If no implementation matches, a new one is created if possible or an exception is thrown
     * otherwise. If more than one implementation is registered and an {@linkplain
     * #setVendorOrdering ordering is set}, then the preferred implementation is returned. Otherwise
     * an arbitrary one is selected.
     *
     * <p>Hints that may be understood includes {@link Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER
     * FORCE_LONGITUDE_FIRST_AXIS_ORDER}, {@link Hints#FORCE_STANDARD_AXIS_UNITS
     * FORCE_STANDARD_AXIS_UNITS}, {@link Hints#FORCE_STANDARD_AXIS_DIRECTIONS
     * FORCE_STANDARD_AXIS_DIRECTIONS} and {@link Hints#VERSION VERSION}.
     *
     * <p><b>TIP:</b> The EPSG official factory and the EPSG extensions (additional CRS provided by
     * ESRI and others) are two distinct factories. Call to {@code getCRSAuthorityFactory("EPSG",
     * null)} returns only one of those, usually the official EPSG factory. If the union of those
     * two factories is wanted, then a chain of fallbacks is wanted. Consider using something like:
     *
     * <blockquote>
     *
     * <code>
     * {@linkplain org.geotools.referencing.factory.FallbackAuthorityFactory#create(Class,
     * java.util.Collection) FallbackAuthorityFactory.create}(CRSAuthorityFactory.class,
     * {@linkplain #getCRSAuthorityFactories getCRSAuthorityFactories}(hints));
     * </code>
     *
     * </blockquote>
     *
     * @param authority The desired authority (e.g. "EPSG").
     * @param hints An optional map of hints, or {@code null} if none.
     * @return The first coordinate reference system authority factory that matches the supplied
     *     hints.
     * @throws FactoryRegistryException if no implementation was found or can be created for the
     *     {@link CRSAuthorityFactory} interface.
     */
    public static CRSAuthorityFactory getCRSAuthorityFactory(
            final String authority, final Hints hints) throws FactoryRegistryException {
        return getAuthorityFactory(
                CRSAuthorityFactory.class, authority, hints, Hints.CRS_AUTHORITY_FACTORY);
    }

    /**
     * Returns a set of all available implementations for the {@link CRSAuthorityFactory} interface.
     * This set can be used to list the available codes known to all authorities. In the event that
     * the same code is understood by more then one authority you will need to assume both are close
     * enough, or make use of this set directly rather than use the {@link CRS#decode} convenience
     * method.
     *
     * @param hints An optional map of hints, or {@code null} if none.
     * @return Set of available coordinate reference system authority factory implementations.
     */
    public static Set<CRSAuthorityFactory> getCRSAuthorityFactories(final Hints hints) {
        return getFactories(CRSAuthorityFactory.class, hints);
    }

    /**
     * Returns the first implementation of {@link CoordinateOperationAuthorityFactory} matching the
     * specified hints. If no implementation matches, a new one is created if possible or an
     * exception is thrown otherwise. If more than one implementation is registered and an
     * {@linkplain #setVendorOrdering ordering is set}, then the preferred implementation is
     * returned. Otherwise an arbitrary one is selected.
     *
     * @param authority The desired authority (e.g. "EPSG").
     * @param hints An optional map of hints, or {@code null} if none.
     * @return The first coordinate operation authority factory that matches the supplied hints.
     * @throws FactoryRegistryException if no implementation was found or can be created for the
     *     {@link CoordinateOperationAuthorityFactory} interface.
     */
    public static CoordinateOperationAuthorityFactory getCoordinateOperationAuthorityFactory(
            final String authority, final Hints hints) throws FactoryRegistryException {
        return getAuthorityFactory(
                CoordinateOperationAuthorityFactory.class,
                authority,
                hints,
                Hints.COORDINATE_OPERATION_AUTHORITY_FACTORY);
    }

    /**
     * Returns a set of all available implementations for the {@link
     * CoordinateOperationAuthorityFactory} interface.
     *
     * @param hints An optional map of hints, or {@code null} if none.
     * @return Set of available coordinate operation authority factory implementations.
     */
    public static Set<CoordinateOperationAuthorityFactory> getCoordinateOperationAuthorityFactories(
            final Hints hints) {
        return getFactories(CoordinateOperationAuthorityFactory.class, hints);
    }

    /**
     * Returns a set of all available implementations for the {@link GridShiftLocator} interface.
     *
     * @param hints An optional map of hints, or {@code null} if none.
     * @return Set of available grid shift factory implementations.
     */
    public static Set<GridShiftLocator> getGridShiftLocators(final Hints hints) {
        return getFactories(GridShiftLocator.class, hints);
    }

    /**
     * Returns the first implementation of {@link MathTransformFactory} matching the specified
     * hints. If no implementation matches, a new one is created if possible or an exception is
     * thrown otherwise. If more than one implementation is registered and an {@linkplain
     * #setVendorOrdering ordering is set}, then the preferred implementation is returned. Otherwise
     * an arbitrary one is selected.
     *
     * @param hints An optional map of hints, or {@code null} if none.
     * @return The first math transform factory that matches the supplied hints.
     * @throws FactoryRegistryException if no implementation was found or can be created for the
     *     {@link MathTransformFactory} interface.
     */
    public static MathTransformFactory getMathTransformFactory(final Hints hints)
            throws FactoryRegistryException {
        return getFactory(MathTransformFactory.class, hints, Hints.MATH_TRANSFORM_FACTORY);
    }

    /**
     * Returns a set of all available implementations for the {@link MathTransformFactory}
     * interface.
     *
     * @param hints An optional map of hints, or {@code null} if none.
     * @return Set of available math transform factory implementations.
     */
    public static Set<MathTransformFactory> getMathTransformFactories(final Hints hints) {
        return getFactories(MathTransformFactory.class, hints);
    }

    /**
     * Sets a pairwise ordering between two vendors. If one or both vendors are not currently
     * registered, or if the desired ordering is already set, nothing happens and {@code false} is
     * returned.
     *
     * <p>The example below said that an ESRI implementation (if available) is preferred over the
     * Geotools one:
     *
     * <blockquote>
     *
     * <code>FactoryFinder.setVendorOrdering("ESRI", "Geotools");</code>
     *
     * </blockquote>
     *
     * @param vendor1 The preferred vendor.
     * @param vendor2 The vendor to which {@code vendor1} is preferred.
     * @return {@code true} if the ordering was set for at least one category.
     */
    public static synchronized boolean setVendorOrdering(
            final String vendor1, final String vendor2) {
        return getServiceRegistry()
                .setOrdering(
                        Factory.class, true, new VendorFilter(vendor1), new VendorFilter(vendor2));
    }

    /**
     * Unsets a pairwise ordering between two vendors. If one or both vendors are not currently
     * registered, or if the desired ordering is already unset, nothing happens and {@code false} is
     * returned.
     *
     * @param vendor1 The preferred vendor.
     * @param vendor2 The vendor to which {@code vendor1} is preferred.
     * @return {@code true} if the ordering was unset for at least one category.
     */
    public static synchronized boolean unsetVendorOrdering(
            final String vendor1, final String vendor2) {
        return getServiceRegistry()
                .setOrdering(
                        Factory.class, false, new VendorFilter(vendor1), new VendorFilter(vendor2));
    }

    /** A filter for factories provided by a given vendor. */
    private static final class VendorFilter implements Predicate<Factory> {
        /** The vendor to filter. */
        private final String vendor;

        /** Constructs a filter for the given vendor. */
        public VendorFilter(final String vendor) {
            this.vendor = vendor;
        }

        /** Returns {@code true} if the specified factory is built by the vendor. */
        @Override
        public boolean test(final Factory factory) {
            return Citations.titleMatches(factory.getVendor(), vendor);
        }
    }

    /**
     * Sets a pairwise ordering between two authorities. If one or both authorities are not
     * currently registered, or if the desired ordering is already set, nothing happens and {@code
     * false} is returned.
     *
     * <p>The example below said that EPSG {@linkplain AuthorityFactory authority factories} are
     * preferred over ESRI ones:
     *
     * <blockquote>
     *
     * <code>FactoryFinder.setAuthorityOrdering("EPSG", "ESRI");</code>
     *
     * </blockquote>
     *
     * @param authority1 The preferred authority.
     * @param authority2 The authority to which {@code authority1} is preferred.
     * @return {@code true} if the ordering was set for at least one category.
     */
    public static synchronized boolean setAuthorityOrdering(
            final String authority1, final String authority2) {
        return getServiceRegistry()
                .setOrdering(
                        AuthorityFactory.class,
                        true,
                        new AuthorityFilter(authority1),
                        new AuthorityFilter(authority2));
    }

    /**
     * Unsets a pairwise ordering between two authorities. If one or both authorities are not
     * currently registered, or if the desired ordering is already unset, nothing happens and {@code
     * false} is returned.
     *
     * @param authority1 The preferred authority.
     * @param authority2 The vendor to which {@code authority1} is preferred.
     * @return {@code true} if the ordering was unset for at least one category.
     */
    public static synchronized boolean unsetAuthorityOrdering(
            final String authority1, final String authority2) {
        return getServiceRegistry()
                .setOrdering(
                        AuthorityFactory.class,
                        false,
                        new AuthorityFilter(authority1),
                        new AuthorityFilter(authority2));
    }

    /** A filter for factories provided for a given authority. */
    private static final class AuthorityFilter implements Predicate<AuthorityFactory> {
        /** The authority to filter. */
        private final String authority;

        /** Constructs a filter for the given authority. */
        public AuthorityFilter(final String authority) {
            this.authority = authority;
        }

        /** Returns {@code true} if the specified provider is for the authority. */
        @Override
        public boolean test(final AuthorityFactory provider) {
            if (authority == null) {
                // If the user didn't specified an authority name, then the factory to use must
                // be specified explicitly through a hint (e.g. Hints.CRS_AUTHORITY_FACTORY).
                return false;
            }
            return Citations.identifierMatches(provider.getAuthority(), authority);
        }
    }

    /**
     * Programmatic management of authority factories. Needed for user managed, not plug-in managed,
     * authority factory. Also useful for test cases.
     *
     * @param authority The authority factory to add.
     */
    public static synchronized void addAuthorityFactory(final AuthorityFactory authority) {
        if (registry == null) {
            scanForPlugins();
        }
        getServiceRegistry().registerFactory(authority);
        authorityNames = null;
    }

    /**
     * Programmatic management of authority factories. Needed for user managed, not plug-in managed,
     * authority factory. Also useful for test cases.
     *
     * @param authority The authority factory to remove.
     */
    public static synchronized void removeAuthorityFactory(final AuthorityFactory authority) {
        getServiceRegistry().deregisterFactory(authority);
        authorityNames = null;
    }

    /**
     * Returns {@code true} if the specified factory is registered. A factory may have been
     * registered by {@link #scanForPlugins()} if it was declared in a {@code META-INF/services}
     * file, or it may have been {@linkplain #addAuthorityFactory added programmatically}.
     *
     * @since 2.4
     */
    public static synchronized boolean isRegistered(final Factory factory) {
        return factory.equals(getServiceRegistry().getFactoryByClass(factory.getClass()));
    }

    /**
     * Scans for factory plug-ins on the application class path. This method is needed because the
     * application class path can theoretically change, or additional plug-ins may become available.
     * Rather than re-scanning the classpath on every invocation of the API, the class path is
     * scanned automatically only on the first invocation. Clients can call this method to prompt a
     * re-scan. Thus this method need only be invoked by sophisticated applications which
     * dynamically make new plug-ins available at runtime.
     */
    public static void scanForPlugins() {
        synchronized (ReferencingFactoryFinder.class) {
            authorityNames = null;
            if (registry != null) {
                registry.scanForPlugins();
            }
        }
        GeoTools.fireConfigurationChanged();
    }

    /**
     * List all available factory implementations in a tabular format. For each factory interface,
     * the first implementation listed is the default one. This method provides a way to check the
     * state of a system, usually for debugging purpose.
     *
     * @param out The output stream where to format the list.
     * @param locale The locale for the list, or {@code null}.
     * @throws IOException if an error occurs while writting to {@code out}.
     */
    public static synchronized void listProviders(final Writer out, final Locale locale)
            throws IOException {
        final FactoryRegistry registry = getServiceRegistry();
        new FactoryPrinter().list(registry, out, locale);
    }

    /** Resets the factory finder and prepares for a new full scan of the SPI subsystems */
    public static void reset() {
        FactoryRegistry copy = registry;
        registry = null;
        if (copy != null) {
            copy.deregisterAll();
        }
    }

    /**
     * Dump to the standard output stream a list of available factory implementations. This method
     * can be invoked from the command line. It provides a mean to verify if some implementations
     * were found in the classpath. The syntax is: <br>
     *
     * <BLOCKQUOTE>
     *
     * <CODE>
     * java org.geotools.referencing.FactoryFinder <VAR>&lt;options&gt;</VAR>
     * </CODE>
     *
     * </BLOCKQUOTE>
     *
     * <p>where options are:
     *
     * <TABLE CELLPADDING='0' CELLSPACING='0'>
     *   <TR><TD NOWRAP><CODE>-encoding</CODE> <VAR>&lt;code&gt;</VAR></TD>
     *       <TD NOWRAP>&nbsp;Set the character encoding</TD></TR>
     *   <TR><TD NOWRAP><CODE>-locale</CODE> <VAR>&lt;language&gt;</VAR></TD>
     *       <TD NOWRAP>&nbsp;Set the language for the output (e.g. "fr" for French)</TD></TR>
     * </TABLE>
     *
     * <p><strong>Note for Windows users:</strong> If the output contains strange symbols, try to
     * supply an "{@code -encoding}" argument. Example:
     *
     * <blockquote>
     *
     * <code>
     * java org.geotools.referencing.FactoryFinder -encoding Cp850
     * </code>
     *
     * </blockquote>
     *
     * <p>The codepage number (850 in the previous example) can be obtained from the DOS commande
     * line using the "{@code chcp}" command with no arguments.
     *
     * @param args Command line arguments.
     */
    public static void main(String... args) {
        final Arguments arguments = new Arguments(args);
        arguments.getRemainingArguments(0);
        try {
            listProviders(arguments.out, arguments.locale);
        } catch (Exception exception) {
            java.util.logging.Logger.getGlobal().log(java.util.logging.Level.INFO, "", exception);
        }
    }
}
